package sudoku;

import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.TreeMap;
import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JToggleButton;
import javax.swing.LayoutStyle;
import javax.swing.ScrollPaneConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 * This class represents solution helper panel on thee main game frame. By using 
 * it user should be able to get number of possible solutions of current board, 
 * or at least approximate prediction of time for panel to take to find all 
 * solutions. User will see what percent of cases covered. After first solution 
 * found user should be able to see complete solution or reveal several cells from it.  
 * @author Taras Basiuk
 * @version 0.1
 */
public class ClassicGridSolutionPanel extends JPanel implements Runnable{
    
    /** 
     *  Toggle button which expands or collapses solution panel without stopping
     *  solution search process. 
     */
    private JToggleButton toggle;
    
    /** JPanel which contains all panel elements except toggle button.*/
    private JPanel leftPanel;
    
    /** This button when pushed starts solve thread, hence solution search process.*/
    private JButton solution;
    
    /** Button which pauses solve Thread, hence pauses solution search process.*/
    private JButton play;
    
    /** Button which pauses solve Thread, hence pauses solution search process.*/
    private JButton pause;
    
    /** 
     * This button when pressed stops solve thread, hence there will be no way
     * to resume it. Solutions which already being found, though, stay in place 
     * and not get erased.
     */
    private JButton stop;
    
    /** 
     * This progress bar shows approximate value of percentage of cases covered
     * in order to find all solutions. 
     */
    private JProgressBar progress;
    
    /** 
     * This text area shows current state of solution search process, number of 
     * solutions found so far, and time prediction needed to check all cases 
     * and, hence, complete solution search process. 
     */
    private JTextArea textArea;
    
    /** Scroll Pane which contains only text area.*/
    private JScrollPane scrollPane;
    
    /** Toggle button which changes visibility of showSolution and solveCell buttons.*/
    private JToggleButton hints;     
    
    /** 
     * This button must create a standalone window with solved copy of current 
     * classic grid.
     */
    private JButton showSolution;    
    
    /** 
     * This button when pressed must create dialog window where user specifies 
     * number of cells to solve. Appropriate number of cells then appears on 
     * current classic grid.
     */
    private JButton solveCell;    
    
    
    /** Link to a main game frame.*/
    private MainFrame frame;
    
    /** Thread which runs gridSolution process.*/
    private final Thread solve;
    
    /** thread which updates state of text area once per second.*/
    private final Thread display;
    
    /** Link to ClassicGrid object.*/
    private ClassicGrid grid;
    
    /** Link to ClassicGridSolution object for current grid.*/
    private ClassicGridSolution gridSolution;
    
    /** Indicates if display of search solution process being paused.*/
    private Boolean wait;
    
    /** Indicates if display of search solution process should be finished completely.*/
    private boolean finish;
    
    /** 
     * Class constructor which connects this panel to main game frame and game
     * classic grid. Initializes and customizes all containers and makes layout.
     */
    public ClassicGridSolutionPanel(final MainFrame frame){
        
        super();
        
        leftPanel = new JPanel();
        solution = new JButton();
        play = new JButton();        
        pause = new JButton();
        stop = new JButton();
        progress = new JProgressBar();
        
        textArea = new JTextArea();
        scrollPane = new JScrollPane(textArea);
        
        hints = new JToggleButton();
        showSolution = new JButton();
        solveCell = new JButton();
        toggle = new JToggleButton();
        
        leftPanel.setVisible(false);

        setMaximumSize(new Dimension(185, 270));
        setMinimumSize(new Dimension(35, 270));
        setPreferredSize(new Dimension(35, 270));

        solution.setText("Find Solution");
        solution.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                double emptyPercentage = ((double)grid.getUnsolvedCells())/grid.getNumCells();                
                Integer dialog = null;                
                if (emptyPercentage > 0.5) { // warn user                    
                    dialog = JOptionPane.showConfirmDialog(frame, ("Solving grids with more than 50% empty cells is not recomended.\n"
                            + "Finding all(or even any) solutions may take very long time in this case.\n"
                            + "Current grid contains " + String.valueOf(emptyPercentage*100).substring(0, 4) + "% of empty cells.\n"
                            + "Do you still want to proceed ?"), "Warning", JOptionPane.YES_NO_OPTION);
                } else solutionButtonPressed();
                if (dialog != null && dialog == JOptionPane.YES_OPTION) solutionButtonPressed();
            }
        });

        play.setIcon(new ImageIcon(getClass().getResource("/sudoku/resourses/play1.jpg"))); 
        play.addActionListener(new ActionListener(){

            @Override
            public void actionPerformed(ActionEvent e) {
                playButtonPressed();
            }
        });
        play.setEnabled(false);

        pause.setIcon(new ImageIcon(getClass().getResource("/sudoku/resourses/pause1.jpg"))); 
        pause.addActionListener(new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
                pauseButtonPressed();
            }
        });
        pause.setEnabled(false);

        stop.setIcon(new ImageIcon(getClass().getResource("/sudoku/resourses/stop1.jpg")));
        stop.addActionListener(new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
                stopButtonPressed();
            }
        });
        
        stop.setEnabled(false);
        
        progress.setStringPainted(true);

        scrollPane.setPreferredSize(new Dimension(120, 55));
        textArea.setEditable(false);
        textArea.setLineWrap(true);
        textArea.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 10));
        scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
        scrollPane.setVisible(false);

        hints.setText("Hints");
        hints.addChangeListener(new ChangeListener(){
            @Override
            public void stateChanged(ChangeEvent e) {
                hintsStateChanged();
            }
        });
        hints.setEnabled(false);

        showSolution.setText("Solution");
        showSolution.addActionListener(new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
                showSolutionButtonPressed();                
            }
        });
        showSolution.setVisible(false);

        solveCell.setText("Solve cell");
        solveCell.addActionListener(new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
                solveCellButtonPressed();                
            }
        });
        solveCell.setVisible(false);

        javax.swing.GroupLayout leftPanelLayout = new GroupLayout(leftPanel);
        leftPanel.setLayout(leftPanelLayout);
        
        leftPanelLayout.setHonorsVisibility(scrollPane, false);
        // horisontal layout setup
        leftPanelLayout.setHorizontalGroup(
            leftPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
            .addGroup(leftPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(leftPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
                    .addGroup(leftPanelLayout.createSequentialGroup()
                        .addComponent(solveCell, GroupLayout.PREFERRED_SIZE, 118, GroupLayout.PREFERRED_SIZE)
                        .addContainerGap())
                    .addGroup(leftPanelLayout.createSequentialGroup()
                        .addGroup(leftPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
                            .addGroup(leftPanelLayout.createSequentialGroup()
                                .addComponent(scrollPane, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
                                .addGap(34, 34, 34))
                            .addGroup(leftPanelLayout.createParallelGroup(GroupLayout.Alignment.TRAILING, false)
                                .addComponent(showSolution, GroupLayout.Alignment.LEADING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addComponent(hints, GroupLayout.Alignment.LEADING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addComponent(progress, GroupLayout.Alignment.LEADING, 0, 0, Short.MAX_VALUE)
                                .addComponent(solution, GroupLayout.Alignment.LEADING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addGroup(GroupLayout.Alignment.LEADING, leftPanelLayout.createSequentialGroup()
                                    .addComponent(play, GroupLayout.PREFERRED_SIZE, 36, GroupLayout.PREFERRED_SIZE)
                                    .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                                    .addComponent(pause, GroupLayout.PREFERRED_SIZE, 35, GroupLayout.PREFERRED_SIZE)
                                    .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                                    .addComponent(stop, GroupLayout.PREFERRED_SIZE, 35, GroupLayout.PREFERRED_SIZE))))
                        .addGap(22, 22, 22))))
        );
        // Vertical layout setup
        leftPanelLayout.setVerticalGroup(
            leftPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
            .addGroup(leftPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(solution)
                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(leftPanelLayout.createParallelGroup(GroupLayout.Alignment.BASELINE)
                    .addComponent(play)
                    .addComponent(pause)
                    .addComponent(stop))
                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(progress, GroupLayout.PREFERRED_SIZE, 23, GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(scrollPane, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(hints)
                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(showSolution)
                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(solveCell)
                .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        toggle.setIcon(new ImageIcon(getClass().getResource("/sudoku/resourses/expand1.jpg")));// NOI18N
        toggle.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                toggleStateChanged();
            }
        });

        GroupLayout layout = new GroupLayout(this);
        this.setLayout(layout);
        
        layout.setHorizontalGroup(
            //layout.createParallelGroup(GroupLayout.Alignment.LEADING)
            /**.addGroup(*/layout.createSequentialGroup()
                .addComponent(leftPanel, GroupLayout.PREFERRED_SIZE, 142, GroupLayout.PREFERRED_SIZE)
                //.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(toggle, GroupLayout.PREFERRED_SIZE, 34, GroupLayout.PREFERRED_SIZE)
                //.addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)//)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(GroupLayout.Alignment.CENTER)
            .addGroup(GroupLayout.Alignment.CENTER, layout.createSequentialGroup()
                .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addComponent(leftPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                )
            .addGroup(GroupLayout.Alignment.CENTER, layout.createSequentialGroup()
                //.addContainerGap(105, Short.MAX_VALUE)
                .addComponent(toggle)
                //.addGap(101, 101, 101)
                )
        );        
        
        this.frame = frame;   
        this.grid = frame.getGridCopy();
        gridSolution = new ClassicGridSolution(this.grid);
        solve = new Thread(gridSolution);
        display = new Thread(this);
        wait = false;
        finish = false;
    }
    
    /** 
     * Constructor which duplicates functionality of default one, but able 
     * to create this panel with expanded or collapsed leftPanel. 
     */
    public ClassicGridSolutionPanel(MainFrame frame, boolean expanded){
        this(frame);
        if (expanded) {
            toggle.setSelected(true);
            leftPanel.setVisible(true);
        };
    }
    
    /** Returns current visibility if leftPanel. */
    public boolean isExpanded(){
        return toggle.isSelected();
    }
    
    /** 
     * This method being called when toggle button pressed. 
     * Sets visibility of left panel and repacks main frame.
     */
    private void toggleStateChanged(){
        if (toggle.isSelected()){
                    leftPanel.setVisible(true);
                    toggle.setIcon(new ImageIcon(getClass().getResource("/sudoku/resourses/colapse1.jpg")));
                    this.setPreferredSize(new Dimension(185, 270));                    
                    frame.pack();
                    /** doesn't work  
                    int x = frame.getX()-150;
                    int y = frame.getY();
                    frame.setLocation(x, y); 
                    */
                } else {
                    leftPanel.setVisible(false);
                    toggle.setIcon(new ImageIcon(getClass().getResource("/sudoku/resourses/expand1.jpg")));
                    this.setPreferredSize(new Dimension(35, 270));                   
                    frame.pack();
                    /** doesn't work
                    int x = frame.getX()+150;
                    int y = frame.getY();
                    frame.setLocation(x, y);
                    */
                }
    }
    
    /** 
     * This method being called when hints toggle button pressed.
     * Sets visibility of showSolution and solveCell buttons. 
     */
    private void hintsStateChanged(){
        if (hints.isSelected()){
            solveCell.setVisible(true);
            showSolution.setVisible(true);
        } else {
            solveCell.setVisible(false);
            showSolution.setVisible(false);
        }
    }
    
    /**
     * This method being called when solution button pressed.
     * Partially resets layout and starts or "restarts" solve and display threads. 
     */
    private synchronized void solutionButtonPressed(){
        
        solution.setEnabled(false);
        pause.setEnabled(true);
        stop.setEnabled(true);
        hints.setEnabled(false);
        hints.setSelected(false);
        
        if (!solve.isAlive()){
            solve.start();
            display.start();
        } else {
            gridSolution.stop(false);
            wait = false;
            notifyAll();
        }
    }
    
    /**
     * This method being called when pause button pressed.
     * Changes layout and pauses solve and display threads. 
     */
    private void pauseButtonPressed(){
        
        play.setEnabled(true);
        pause.setEnabled(false);        
        
        wait = true; 
        gridSolution.plug(false);
        
        textArea.setText("Paused...\nSolutions found: " + gridSolution.getNumSolFound());                  
    }
    
    /**
     * This method being called when play button pressed.
     * Changes layout and resumes solve and display threads. 
     */
    private synchronized void playButtonPressed(){
        play.setEnabled(false);
        pause.setEnabled(true);     
        
        wait = false;      
        notifyAll();
        
        gridSolution.plug(true);   
    }
    
    /** finishes displaying thread completely. */
    public void finish(){
        gridSolution.finish();
        finish = true;
    }
    
    /**
     * This method being called when stop button pressed.
     * Changes layout and stops solve and display threads. 
     */
    private synchronized void stopButtonPressed(){
        play.setEnabled(false);
        pause.setEnabled(false);
        stop.setEnabled(false);
        solution.setEnabled(true);
      
        gridSolution.stop(true);        
        wait = true;           
        
        textArea.setText("Stoped.\nSolutions found: " + gridSolution.getNumSolFound()); 
    }
    
    /** 
     * This method creates dialog window where user chooses number of solution 
     * found to display. Then chosen solution being displayed in standalone window. 
     */
    private void showSolutionButtonPressed(){
        String choiseStr = JOptionPane.showInputDialog(frame,"Enter solution number (from 1 to "
                + gridSolution.getNumSolFound() +") to show:", "1");        
        try {
            int choiseInt = Integer.parseInt(choiseStr);
            if (choiseInt >= 1 && choiseInt <= gridSolution.getNumSolFound()){
                
                TreeMap<Integer, ClassicCell> sol = gridSolution.getSolutions().get(choiseInt-1);                
                ClassicGrid solvedGrid = new ClassicGrid(sol); 
                MainFrame.drawCanvas(solvedGrid);
                
            } else JOptionPane.showMessageDialog(frame, "Input out of bounds.");
        } catch (NumberFormatException e){
            JOptionPane.showMessageDialog(frame, "Invalid input.");
        }
    } 
    
    /** 
     * This method creates dialog window where user chooses number of cells to 
     * reveal at random. Then chosen number of cells being revealed on main canvas.
     * @todo Currently reveals only cells from first found solution.
     */
    private void solveCellButtonPressed(){
        
        String choiseStr = JOptionPane.showInputDialog(frame, "Enter number of cells (from 1 to "
                + grid.getUnsolvedCells() +") to reveal:", "1");        
        try {
            int choiseInt = Integer.parseInt(choiseStr);
            if (choiseInt >= 1 && choiseInt <= grid.getUnsolvedCells()){
                
                TreeMap<Integer, ClassicCell> sol = gridSolution.getSolutions().get(0); // change '0' later
                ArrayList<ClassicCell> emptyCells = new ArrayList();
                int i = 0;
                
                for (int j=0; j< grid.getNumCells(); j++){
                    ClassicCell c = grid.getCopyCell(j);
                    if (c.getValue() == 0){
                        emptyCells.add(c);
                        i++;
                    }
                }
                
                for (int j=0; j<choiseInt; j++){
                    int random = (int) (Math.random()*i);
                    int number = emptyCells.get(random).getRowPosition();
                    frame.insertToGrid(number, sol.get(number).getValue(), false);
                    emptyCells.remove(random);
                    i--;                    
                }
                
                frame.updateCanvas();
                frame.updateSolutionPanel();
                
            } else JOptionPane.showMessageDialog(frame, "Input out of bounds.");
        } catch (NumberFormatException e){
            JOptionPane.showMessageDialog(frame, "Invalid input.");
        }
    }
    
    /** Method demanded by Runnable interface which makes display of solve thread progress. */
    @Override
    public void run() {
        //System.out.println("Display started");
        scrollPane.setVisible(true);

        boolean f = hints.isEnabled();

        int currentPercentage;
        do {
            try {
                currentPercentage = (int) gridSolution.getPercentageCovered();
                progress.setValue(currentPercentage);

                if (!wait) {
                    long miliSeconds = (long) gridSolution.getTimePrediction();
                    long seconds = miliSeconds / 1000;
                    long minutes = seconds / 60;
                    long hours = minutes / 60;
                    long days = hours / 24;
                    
                    minutes = minutes % 60;
                    hours = hours % 24;

                    if (!f && gridSolution.getNumSolFound() != 0) {
                        hints.setEnabled(true);
                    }

                    String text = "Solving...\n"
                            + "Solutions found: " + gridSolution.getNumSolFound() + "\n"
                            + "Approx. time left:\n"
                            + days + "d : "
                            + hours + "h : "
                            + minutes + "m";
                            
                    textArea.setText(text);
                    
                    Thread.sleep(1000);
                } else {
                    synchronized (this) {
                        this.wait();
                    }
                }
            } catch (InterruptedException ex) {
                return;
            }
            if (finish) currentPercentage = 100;
        } while (currentPercentage != 100);        
        // Solutions search complete 
        textArea.setText("Complete\nSolutions found: " + gridSolution.getNumSolFound());
        pause.setEnabled(false);
        stop.setEnabled(false);
        //System.out.println("Display stopped");
    }
}
